using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Crank.Core
{
    public static class Inflector
    {
        #region Static Constructor

        static Inflector()
        {
            ResetLists();
        }

        #endregion

        #region List Management

        public static void Plural(string rule, string replacement)
        {
            plurals.Insert(0, new Inflection(rule, replacement));
        }

        public static void Singular(string rule, string replacement)
        {
            singulars.Insert(0, new Inflection(rule, replacement));
        }

        public static void Uncountable(params string[] words)
        {
            foreach (string word in words)
                uncountables.Add(word.ToLower());
        }

        public static void Irregular(string singular, string plural)
        {
            Plural(
                String.Format("({0}){1}$", singular[0], singular.Remove(0, 1)),
                String.Format("$1{0}", plural.Remove(0, 1)));
            Singular(
                String.Format("({0}){1}$", plural[0], plural.Remove(0, 1)),
                String.Format("$1{0}", singular.Remove(0, 1)));
        }

        #endregion

        #region Tests

        public static bool IsUncountable(string word)
        {
            return uncountables.Contains(word.ToLower());
        }

        #endregion

        #region Conversions

        public static string Pluralize(string word)
        {
            return IsUncountable(word) ? word : ApplyInflections(word, plurals);
        }

        public static string Singularize(string word)
        {
            return IsUncountable(word) ? word : ApplyInflections(word, singulars);
        }

        private static string ApplyInflections(string word, IList<Inflection> inflections)
        {
            foreach (Inflection inflection in inflections)
                if (inflection.Matches(word))
                    return inflection.Apply(word);
            return word;
        }

        #endregion

        public static void ResetLists()
        {
            plurals = new List<Inflection>();
            singulars = new List<Inflection>();
            uncountables = new List<string>();

            Plural("$", "s");
            Plural("s$", "s");
            Plural("(ax|test)is$", "$1es");
            Plural("(octop|vir)us$", "$1i");
            Plural("(alias|status)$", "$1es");
            Plural("(bu)s$", "$1ses");
            Plural("(buffal|tomat)o$", "$1oes");
            Plural("([ti])um$", "$1a");
            Plural("sis$", "ses");
            Plural("(?:([^f])fe|([lr])f)$", "$1$2ves");
            Plural("(hive)$", "$1s");
            Plural("([^aeiouy]|qu)y$", "$1ies");
            Plural("(matr|vert|ind)ix|ex$", "$1ices");
            Plural("([m|l])ouse$", "$1ice");
            Plural("^(ox)$", "$1en");
            Plural("(quiz)$", "$1zes");

            Singular("s$", "");
            Singular("(n)ews$", "$1ews");
            Singular("([ti])a$", "$1um");
            Singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
            Singular("(^analy)ses$", "$1sis");
            Singular("([^f])ves$", "$1fe");
            Singular("(hive)s$", "$1");
            Singular("(tive)s$", "$1f");
            Singular("([lr])ves$", "$1f");
            Singular("([^aeiouy]|qu)ies$", "$1y");
            Singular("(s)eries$", "$1eries");
            Singular("(m)ovies$", "$1ovie");
            Singular("(x|ch|ss|sh)es$", "$1");
            Singular("([m|l])ice$", "$1ouse");
            Singular("(bus)es$", "$1");
            Singular("(o)es$", "$1");
            Singular("(shoe)s$", "$1");
            Singular("(cris|ax|test)es$", "$1is");
            Singular("(octop|vir)i$", "$1us");
            Singular("(alias|status)es$", "$1");
            Singular("^(ox)en$", "$1");
            Singular("(vert|ind)ices$", "$1ex");
            Singular("(matr)ices$", "$1ix");
            Singular("(quiz)zes$", "$1");

            Irregular("person", "people");
            Irregular("man", "men");
            Irregular("child", "children");
            Irregular("sex", "sexes");
            Irregular("move", "moves");

            Uncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep");
        }

        public static string StripSurroundingWhiteSpace(string initialString)
        {
            return Regex.Replace(initialString, @"^\s*(.*)\s*$", "$1");
        }

        #region Private Data

        private static IList<Inflection> plurals;
        private static IList<Inflection> singulars;
        private static IList<string> uncountables;

        private struct Inflection
        {
            public readonly string Replacement;
            public readonly Regex Rule;

            public Inflection(string rule, string replacement)
            {
                Rule = new Regex(rule, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                Replacement = replacement;
            }

            public bool Matches(string s)
            {
                return Rule.IsMatch(s);
            }

            public string Apply(string s)
            {
                return Rule.Replace(s, Replacement);
            }
        }

        #endregion

    }
}
